﻿using System;
using System.ComponentModel;
using System.Text;

using Exif.Interfaces;
using Exif.Various;

namespace Exif.Tiff
{
	///<summary>
	///Тег
	///</summary>
	[TypeConverter(typeof(ExpandableObjectConverter))]
	[ReadOnly(true)]
	public class TagEntry
	{
		#region Конструктор

		public TagEntry(IfdBase ifd)
		{
			Ifd = ifd;
		}

		#endregion

		#region Открытые свойства класса

		public IfdBase Ifd { get; set; }

		///<summary>
		///ID Тега
		///</summary>
		public UInt16 TagID { get; set; }

		///<summary>
		///Тип тега (<see cref="TagType"/>)
		///</summary>
		public UInt16 Type { get; set; }

		///<summary>
		///Количество элементов <see cref="Type"/>
		///</summary>
		public UInt32 Count { get; set; }

		///<summary>
		///Размер хранимой информации
		///</summary>
		public virtual UInt32 Length
		{
			get
			{
				if (this.Type == (ushort)TagType.Ascii || this.Type == (ushort)TagType.Undefined)
				{
					return this.Count;
				}

				return TagTypeSizeAttribute.GetTagSize((TagType)this.Type)*this.Count;
			}
		}

		///<summary>
		///Хранимые данные
		///</summary>
		public virtual object Data { get; set; }

		#endregion

		#region Внутренние свойства класса

		internal UInt32 ValueOffset { get; set; }

		#endregion

		#region Внутренние методы класса

		///<summary>
		///Считывает данные
		///</summary>
		///<param name="reader"></param>
		///<exception cref="NotImplementedException"></exception>
		internal void ReadData(IImageReader reader)
		{
			if (this.Length > 4)
			{
				this.ReadTypedData(reader, Ifd.Parent.AbsOffset(this.ValueOffset), (int)this.Length);
			}
			else
			{
				switch (this.Type)
				{
					case (ushort)TagType.Byte:
						this.Data = (byte)this.ValueOffset;
						break;
					case (ushort)TagType.Ascii:
						this.Data = AsciiStringFrom4Bytes(this.ValueOffset, this.Count);
						break;
					case (ushort)TagType.Short:
						this.Data = (UInt16)this.ValueOffset;
						break;
					case (ushort)TagType.Undefined:
						this.Data = BytesArrayFrom4Bytes(this.ValueOffset, this.Count);
						break;
					case (ushort)TagType.Long:
						this.Data = (UInt32)this.ValueOffset;
						break;
					case (ushort)TagType.SLong:
						this.Data = (Int32)this.ValueOffset;
						break;

					case (ushort)TagType.Rational:
					case (ushort)TagType.SRational:
						throw new Exception("Сюда мы никогда не попадем...");

					default:
						throw new ArgumentOutOfRangeException("Type", this.Type,
							string.Format("Неизвестный тип поля: {0}", this.Type));
				}
			}
		}

		private static object AsciiStringFrom4Bytes(uint value, uint count)
		{
			var bytes = BytesArrayFrom4Bytes(value, count);
			return Encoding.ASCII.GetString(bytes);
		}

		private static byte[] BytesArrayFrom4Bytes(uint value, uint count)
		{
			var bytes = new byte[count];

			for (; count > 0;)
			{
				bytes[bytes.Length - count] = (byte)((value >> (int)(--count*8)) & 0xFF);
			}

			return bytes;
		}

		private void ReadTypedData(IImageReader reader, long offset, int length)
		{
			switch (this.Type)
			{
				case (ushort)TagType.Ascii:
					this.Data = reader.ReadString(offset, length, Encoding.ASCII);
					break;

				case (ushort)TagType.Undefined:
					this.Data = reader.ReadBytes(offset, length);
					break;

				case (ushort)TagType.Rational:
					this.Data = reader.ReadRational(offset);
					break;

				case (ushort)TagType.SRational:
					this.Data = reader.ReadSRational(offset);
					break;

				case (ushort)TagType.Long:
				case (ushort)TagType.Byte:
				case (ushort)TagType.Short:
				case (ushort)TagType.SLong:
					throw new Exception("Сюда мы никогда не попадем...");

				default:
					throw new ArgumentOutOfRangeException("Type", this.Type,
						string.Format("Неизвестный тип поля: {0}", this.Type));
			}
		}

		private static Int32 ReadInt32(byte[] bytes, int offset, Endian endian)
		{
			if (bytes.Length < 4 + offset)
			{
				throw new ArgumentOutOfRangeException("offset",
					"Четыре байта по данному выходят за пределы массива o.O");
			}

			switch (endian)
			{
				case Endian.Big:
					return (bytes[offset++] << 0x18) | (bytes[offset++] << 0x10) | (bytes[offset++] << 0x8) |
						bytes[offset];
				case Endian.Little:
					return bytes[offset++] | (bytes[offset++] << 0x8) | (bytes[offset++] << 0x10) |
						(bytes[offset] << 0x18);
				default:
					throw new ArgumentOutOfRangeException("endian");
			}
		}

		private static UInt32 ReadUInt32(byte[] bytes, int offset, Endian endian)
		{
			if (bytes.Length < 4 + offset)
			{
				throw new ArgumentOutOfRangeException("offset",
					"Четыре байта по данному выходят за пределы массива o.O");
			}

			switch (endian)
			{
				case Endian.Big:
					return
						(UInt32)
							((bytes[offset++] << 0x18) | (bytes[offset++] << 0x10) | (bytes[offset++] << 0x8) |
								bytes[offset]);
				case Endian.Little:
					return
						(UInt32)
							(bytes[offset++] | (bytes[offset++] << 0x8) | (bytes[offset++] << 0x10) |
								(bytes[offset] << 0x18));
				default:
					throw new ArgumentOutOfRangeException("endian");
			}
		}

		#endregion

		#region Перегруженные методы

		public override string ToString()
		{
			return Data == null ? base.ToString() : Data.ToString();
		}

		#endregion
	}
}